﻿using FlyIL.Emu;
using FlyRemote.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using UnicornFW.Registers;
using UnicornNet;
using UnicornNet.Data;


using static FlyIL.RegCode;

namespace TestEmu
{
    public class UnicornWrapper : IDisposable
    {

        Unicorn unicorn;
        public bool Is64 { get; }
        public ReadGroup ReadGroup { get; }

        IntPtr hook;


        Dictionary<ulong, byte> backCodes = new Dictionary<ulong, byte>();
        public void Dispose()
        {
            unicorn?.Dispose();
        }

        public UnicornWrapper(bool is64, ReadGroup group, int StackSize = 0x2000)
        {
            Is64 = is64;

            if (is64)
            {
                unicorn = new Unicorn(UcArch.UC_ARCH_X86, UcMode.UC_MODE_64);

                ulong stackStart = 0x10_0000_0000;
                var _vsp = (ulong)stackStart + (ulong)StackSize - 0x100;

                unicorn.MemMap(stackStart, (ulong)StackSize);
                unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_RSP, _vsp);
            }
            else
            {
                unicorn = new Unicorn(UcArch.UC_ARCH_X86, UcMode.UC_MODE_32);

                ulong stackStart = 0x10_0000;
                var _vsp = (ulong)stackStart + (ulong)StackSize - 0x100;

                unicorn.MemMap(stackStart, (ulong)StackSize);
                unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_ESP, _vsp);
            }


            ReadGroup = group;

            //unicorn.MemMap(ReadGroup.BaseAddress, (ulong)group.RegionSize);

            IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(group.CurrentValues, 0);
            unicorn.MemMapPtr(group.BaseAddress, (ulong)group.RegionSize, ptr);

        }


        public void CopyFrom(Cpu cpu)
        {
            if (this.Is64 != cpu.Is64)
                return;

            if (!Is64)
            {
                this.EAX = cpu.GetReg32(r1d);
                this.EBX = cpu.GetReg32(r2d);
                this.ECX = cpu.GetReg32(r3d);
                this.EDX = cpu.GetReg32(r4d);
                this.EBP = cpu.GetReg32(r5d);
                this.ESI = cpu.GetReg32(r6d);
                this.EDI = cpu.GetReg32(r7d);

                this.Esp = (uint)cpu.Vsp;
                this.flags = (uint)cpu.rflags;

                int size = (int)(cpu._stack.BaseAddress + (ulong)cpu._stack.RegionSize - (cpu.Vsp - 0x10));
                size -= 0x100;
                /// 复制中 ， 要包括 Esp 的 上部空间， 防止因 直接提栈 ， 造成栈无法匹配 
                byte[] arr = cpu._stack.ReadBytes(Esp - 0x10,  size );

                unicorn.MemWrite(Esp - 0x10, arr, (ulong)arr.Length);

            }
        }


        /// <summary>
        /// 使用 codes  模拟执行 
        /// </summary>
        /// <param name="is64"></param>
        /// <param name="code"></param>
        /// <param name="address"></param>
        /// <param name="StackSize"></param>
        public UnicornWrapper(bool is64, byte[] code, ulong address = 0x40000, int StackSize = 0x2000)
        {
            Is64 = is64;

            if (is64)
            {
                unicorn = new Unicorn(UcArch.UC_ARCH_X86, UcMode.UC_MODE_64);

                ulong stackStart = 0x10_0000_0000;
                var _vsp = (ulong)stackStart + (ulong)StackSize - 0x100;

                unicorn.MemMap(stackStart, (ulong)StackSize);
                unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_RSP, _vsp);
            }
            else
            {
                unicorn = new Unicorn(UcArch.UC_ARCH_X86, UcMode.UC_MODE_32);

                ulong stackStart = 0x10_0000;
                var _vsp = (ulong)stackStart + (ulong)StackSize - 0x100;

                unicorn.MemMap(stackStart, (ulong)StackSize);
                unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_ESP, _vsp);
            }



            unicorn.MemMap(address, 0x1000);
            unicorn.MemWrite(address, code, (ulong)code.Length);

        }

        /// <summary>
        /// 运行 
        /// </summary>
        /// <param name="entryPoint"></param>
        /// <param name="stop">!!! 这条语句不执行，停止 </param>
        public void RunWithLog(ulong entryPoint, ulong stop)
        {


            hook = unicorn.HookCode((uc, address1, size, data) =>
            {
                if (address1 == 0x042B5A37)
                    ;

                if (address1 == 0x042B5ADD)
                    ;
                Console.WriteLine($"\t {address1:x}");
            });

            foreach (var u in backCodes)
            {
                unicorn.MemWrite(u.Key, new byte[] { u.Value }, 1);
            }

            byte code = unicorn.MemRead(stop, 1)[0];
            backCodes[stop] = code;
            unicorn.MemWrite(stop, new byte[] { 0xCC }, 1);

            unicorn.EmuStart(entryPoint, stop);
        }


        /// <summary>
        /// 运行 
        /// </summary>
        /// <param name="entryPoint"></param>
        /// <param name="stop">!!! 这条语句不执行，停止 </param>
        public void Run(ulong entryPoint, ulong stop, int size = 0)
        {

            //if (hook != IntPtr.Zero)
            //{
            //    unicorn.HookDel(hook);
            //    hook = IntPtr.Zero;
            //}

            //foreach (var u in backCodes)
            //{
            //    unicorn.MemWrite(u.Key, new byte[] { u.Value }, 1);
            //}

            //byte code = unicorn.MemRead(stop, 1)[0];
            //backCodes[stop] = code;
            //unicorn.MemWrite(stop, new byte[] { 0xCC }, 1);


            unicorn.EmuStart(entryPoint, stop, 0, (ulong)size);
        }


        public bool Compare(Cpu cpu, out string result)
        {
            result = null;

            if (this.Is64 != cpu.Is64)
                return false;

            if (!Is64)
            {
                StringBuilder sb = new StringBuilder();

                bool a = (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EAX) == cpu.GetReg32(r1d);

                if (!a) sb.Append("eax ");


                bool b = (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EBX) == cpu.GetReg32(r2d);
                if (!b) sb.Append("ebx ");

                bool c = (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_ECX) == cpu.GetReg32(r3d);
                if (!c) sb.Append("ecx ");

                bool d = (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EDX) == cpu.GetReg32(r4d);
                if (!d) sb.Append("edx ");

                bool e = (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EBP) == cpu.GetReg32(r5d);
                if (!e) sb.Append("ebp ");

                bool f = (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_ESI) == cpu.GetReg32(r6d);
                if (!f) sb.Append("esi ");

                bool g = (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EDI) == cpu.GetReg32(r7d);
                if (!g) sb.Append("edi ");

                bool fl = unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EFLAGS) == cpu.rflags;
                if (!fl) sb.Append("flags ");

                bool sp = unicorn.RegRead((int)UcX86Regs.UC_X86_REG_ESP) == cpu.Vsp;
                if (!sp) sb.Append("esp ");


                result = sb.ToString();
                return a && b && c && d && e && f && g /*&& fl*/ && sp;
            }



            return false;
        }

        public bool CompareStack(Cpu cpu)
        {


            if (this.Is64 != cpu.Is64)
                return false;

            if (!Is64)
            {


                bool sp = unicorn.RegRead((int)UcX86Regs.UC_X86_REG_ESP) == cpu.Vsp;

                if (sp)
                {

                    int size = (int)(cpu._stack.BaseAddress + (ulong)cpu._stack.RegionSize - cpu.Vsp);
                    size -= 0x100;

                    byte[] arr = cpu._stack.ReadBytes(cpu.Vsp, size);

                    byte[] arr2 = unicorn.MemRead(cpu.Vsp, (ulong)arr.Length);

                    return arr.SequenceEqual(arr2);
                }

            }


            return false;
        }

        public uint flags
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EFLAGS);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_EFLAGS, value);
        }


        public uint Esp
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_ESP);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_ESP, value);
        }

        public uint EAX
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EAX);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_EAX, value);
        }
        public uint EBX
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EBX);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_EBX, value);
        }

        public uint ECX
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_ECX);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_ECX, value);
        }
        public uint EDX
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EDX);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_EDX, value);
        }


        public uint EBP
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EBP);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_EBP, value);
        }

        public uint EDI
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_EDI);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_EDI, value);
        }
        public uint ESI
        {
            get => (uint)unicorn.RegRead((int)UcX86Regs.UC_X86_REG_ESI);
            set => unicorn.RegWrite((int)UcX86Regs.UC_X86_REG_ESI, value);
        }


        public uint Esp_0
        {
            get => BitConverter.ToUInt32(unicorn.MemRead(Esp, 4), 0);
        }

        public uint Esp_4
        {
            get => BitConverter.ToUInt32(unicorn.MemRead(Esp + 4, 4), 0);
        }

        public uint Esp_8
        {
            get => BitConverter.ToUInt32(unicorn.MemRead(Esp + 8, 4), 0);
        }

        public uint Esp_C
        {
            get => BitConverter.ToUInt32(unicorn.MemRead(Esp + 0xC, 4), 0);
        }
    }
}
